﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using DARE;
using System.IO;
using System.Xml.Serialization;
using System.Xml;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using System.Threading.Tasks;
using System.Reflection;

namespace ContentProcessor
{
    /// <summary>
    /// Model Processor
    /// This processor extract Model information
    /// </summary>
    [ContentProcessor(DisplayName = "Full Model Information And Animation - DARE")]
    public sealed class CModelAnimationProcessor : CModelFullExporter
    {
        #region Fields

        private readonly TimeSpan TIMEPERSECOND = TimeSpan.FromSeconds(1 / 30.0f);

        private CModelTag m_modelTag;
        //private bool modelSplit = false;
        private List<MeshContent> m_meshes;
        private ContentProcessorContext m_context;
        private AnimationContentDictionary m_animations = new AnimationContentDictionary();

        #endregion

        #region Property

        ///// <summary>
        ///// Path of the file that describe how to split the current animation
        ///// </summary>
        //public string AnimationsDescriptionFilePath { get; set; }

        /// <summary>
        /// Property used to set the animation's name 
        /// </summary>
        public string AnimationName { get; set; }

        ///// <summary>
        ///// If Enable, the model of the skeleton would be generated
        ///// </summary>
        //[DefaultValue(false)]
        //public bool GenerateSkeletonModel { get; set; }

        ///// <summary>
        ///// scale of each bone
        ///// </summary>
        ///// <value>Default value 1.5f</value>
        //[DefaultValue(1.5f)]
        //public float BonesSkeletonScale { get; set; }

        #endregion

        #region Method

        /// <summary>
        /// Method use to transform input model data into ModelContent
        /// </summary>
        /// <param name="input">Model input data</param>
        /// <param name="context">Build's content</param>
        /// <returns>ModelContent</returns>
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            this.m_context = context;
            this.m_meshes = new List<MeshContent>();

            this.m_modelTag = new CModelTag();

            CHierarchy _hierarchy = new CHierarchy(CModelAnimationProcessor.GenerateAnimNodes(input).ToArray());
            m_modelTag.Add(CAnimationMgr.HIERARCHY, _hierarchy);

            CAnimationProcessor animProcessor = null;
            if (AsAnimations(input))
            {
                animProcessor = new CAnimationProcessor();
                //animProcessor.AnimationsDescriptionFilePath = this.AnimationsDescriptionFilePath;
                animProcessor.AnimationName = this.AnimationName;
                m_modelTag.Add(CAnimationMgr.ANIMATION, animProcessor.Process(input, context));
            }

            ModelContent _model = base.Process(input, context);

            if (_model.Tag is CModelTag)
            {
                CModelTag _currentTag = _model.Tag as CModelTag;
                foreach (var elem in m_modelTag)
                {
                    if (_currentTag.ContainsKey(elem.Key))
                        _currentTag[elem.Key] = elem.Value;
                    else
                        _currentTag.Add(elem.Key, elem.Value);
                }
            }
            else
                _model.Tag = m_modelTag;

            if (animProcessor != null)
                animProcessor.InfoSkinning(_model);

            return _model;
        }



        /// <summary>
        /// Search from the tree node if the model contain animation information
        /// </summary>
        /// <param name="node">root of the model content</param>
        /// <returns></returns>
        private bool AsAnimations(NodeContent node)
        {
            if (node.Animations.Count > 0)
                return true;
            foreach (NodeContent child in node.Children)
            {
                if (AsAnimations(child))
                    return true;
            }
            return false;
        }

        ///// <summary>
        ///// Search from the tree node if the model contain animation information
        ///// </summary>
        ///// <param name="node">root of the model content</param>
        ///// <returns></returns>
        //private bool AsSkeleton(NodeContent node)
        //{
        //    if (node is BoneContent)
        //        return true;
        //    foreach (NodeContent child in node.Children)
        //    {
        //        if (AsSkeleton(child))
        //            return true;
        //    }
        //    return false;
        //}
        public static List<CAnimNode> GenerateAnimNodes(NodeContent input)
        {
            List<NodeContent> _content = new List<NodeContent>();
            List<CAnimNode> _anim = new List<CAnimNode>();
            GenerateHierachiAnimNode(input, _content);
            for (int i = 0; i < _content.Count; ++i)
            {
                NodeContent elem = _content[i];
                _anim.Add(new CAnimNode(elem.Name)
                    {
                        Index = i,
                        IsBones = elem is BoneContent,
                        ParentIndex = _content.IndexOf(elem.Parent),
                        BindPose = new CTransform(elem.Transform),
                        InvBindPose = Matrix.Invert(elem.AbsoluteTransform),
                        AbsolutePose = new CTransform(elem.AbsoluteTransform)
                    });
            }
            return _anim;

        }

        public static void GenerateHierachiAnimNode(NodeContent input, List<NodeContent> _content)
        {
            _content.Add(input);
            for (int i = 0; i < input.Children.Count; ++i)
            {
                NodeContent _c = input.Children[i];
                GenerateHierachiAnimNode(_c, _content);
            }
        }

        private void AnimationMinMax(ModelContent _model, ref Vector3 min, ref Vector3 max)
        {
            if (!m_modelTag.ContainsKey(CAnimationMgr.ANIMATION))
                return;

            Vector3 _min = min;
            Vector3 _max = max;

            var _up = typeof(CAnimConfig).GetMethod("Update", BindingFlags.NonPublic | BindingFlags.Instance);
            var _anim = m_modelTag[CAnimationMgr.ANIMATION] as CAnimationContent;
            var _hierarchi = m_modelTag[CAnimationMgr.HIERARCHY] as CHierarchy;

            foreach (var _animation in _anim.Animations)
            {

                var _config = _animation.GetAnims().First().Clone() as CAnimConfig;
                Parallel.ForEach((_animation as CAnimation).AllFrames, (frame) =>
                    {
                        var _bones = new Matrix[_hierarchi.NodesCount];
                        foreach (var node in _hierarchi.Nodes)
                        {
                            if (node.ParentIndex == -1)
                            {
                                if (frame != null)
                                    _bones[node.Index] = frame.Pose[node.Index].Transform;
                                else
                                    _bones[node.Index] = node.BindPose.Transform;
                            }
                            else
                            {
                                if (frame != null && frame.Pose.Count > node.Index && frame.Pose[node.Index] != null)
                                    _bones[node.Index] = frame.Pose[node.Index].Transform * _bones[node.ParentIndex];
                                else
                                    _bones[node.Index] = node.BindPose.Transform * _bones[node.ParentIndex];
                            }
                        }

                        foreach (var _met in _bones)
                        {
                            Vector3 vertice = _met.Translation;
                            if (vertice.X < _min.X)
                                _min.X = vertice.X;
                            if (vertice.Y < _min.Y)
                                _min.Y = vertice.Y;
                            if (vertice.Z < _min.Z)
                                _min.Z = vertice.Z;

                            if (vertice.X > _max.X)
                                _max.X = vertice.X;
                            if (vertice.Y > _max.Y)
                                _max.Y = vertice.Y;
                            if (vertice.Z > _max.Z)
                                _max.Z = vertice.Z;
                        }
                    });
            }
            min = _min;
            max = _max;
        }

        #region Debug

        private void DrawHierachie(NodeContent node, StreamWriter sw, int index, string filename = @"C:\TestFolder\Hiereachie.xml")
        {
            bool _sw = (sw == null);
            if (_sw)
            {
                if (File.Exists(filename))
                    File.Delete(filename);
                if (!Directory.Exists(Path.GetDirectoryName(filename)))
                    Directory.CreateDirectory(Path.GetDirectoryName(filename));
                sw = new StreamWriter(File.Open(filename, FileMode.Create));
            }
            string str = "";
            for (int i = 0; i < index; ++i)
                str += " ";

            string _info = "";
            //if (node is MeshContent)
            //{
            //    MeshContent _m = (MeshContent)node;
            //    foreach (var v in _m.Geometry)
            //    {
            //        if (v.Vertices.Channels.Contains(VertexChannelNames.Weights(0)))
            //        {
            //            VertexChannel<BoneWeightCollection> weight = v.Vertices.Channels.Get<BoneWeightCollection>(VertexChannelNames.Weights(0));
            //            foreach (var w in weight)
            //            {
            //                string s = "";
            //                foreach (var t in w)
            //                {
            //                    s += str + "BoneName = " + t.BoneName + " Weight = " + t.Weight + "\n";
            //                }
            //                _info += s;
            //            }
            //        }
            //    }
            //}


            sw.WriteLine("{0}name = {1} type = {2} {3}", str, node.Name, node, _info);
            foreach (var child in node.Children)
                DrawHierachie(child, sw, index + 4);
            if (_sw)
                sw.Dispose();
        }

        private void InfosModel(ModelContent _modelContent, string file)
        {
            using (StreamWriter sr = new StreamWriter(File.Open(file, FileMode.OpenOrCreate)))
            {
                sr.WriteLine("----- Bones -----\n");
                foreach (var bone in _modelContent.Bones)
                {
                    sr.WriteLine("Bone name => " + bone.Name + " Parent name => " +
                                ((bone.Parent != null) ? bone.Parent.Name : "none") +
                                "Parent Index => " + ((bone.Parent != null) ? bone.Parent.Index : 0) +
                                " index => " + bone.Index);
                }

                sr.WriteLine("----- Mesh -----\n");
                foreach (var mesh in _modelContent.Meshes)
                {
                    sr.WriteLine("Mesh Name => " + mesh.Name);
                    //foreach (var v in mesh.SourceMesh.Geometry)
                    //{
                    //    VertexChannel<> _channel = v.Vertices.Channels.Get<>(VertexChannelNames.Color(0));
                    //}
                }
            }
        }

        #endregion

        #endregion
    }
}
